<?php

namespace ApiSdk\Kwaixiaodian\Servers;

use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Client;
use Exception;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Redis\RedisManager;
use Log;

class BaseService
{
    /**
     * 请求类型
     */
    const METHOD_GET = 'GET';
    const METHOD_POST = 'POST';
    const METHOD_PUT = 'PUT';
    const METHOD_DELETE = 'DELETE';

    private $appKey;//应用程序ID
    private $appSecret;//客户ID
    private $token;
    private $version;
    private $signMethod;
    private $signSecret;

    private $base_url;
    private $apiParams;     //api请求参数
    private $headerParams = [];  //头部请求信息
    private $commonParams;

    private $_httpClient;
    private $maxRetryTimes = 0;
    private $retryInterval = 1;
    private $http_errors   = false;
    private $redirect_uri;

    /**
     * @throws Exception
     */
    function __construct()
    {
        $this->commonParams = [];
        $this->base_url     = config('platform.kwaixiaodian.base_url');
        $this->appKey       = config('platform.kwaixiaodian.appKey');
        $this->appSecret    = config('platform.kwaixiaodian.appSecret');
        $this->version      = config('platform.kwaixiaodian.version', 1);
        $this->signMethod   = config('platform.kwaixiaodian.signMethod', 'MD5');
        $this->signSecret   = config('platform.kwaixiaodian.signSecret');
        $this->redirect_uri = config('platform.kwaixiaodian.redirectUri');
        $this->_httpClient  = new Client([
            'base_uri' => $this->base_url
        ]);
        $this->setMaxRetryTimes(config('platform.kwaixiaodian.maxRetryTimes', 1));
        $this->setRetryInterval(config('platform.kwaixiaodian.retryInterval', 1));
    }


    /**
     * Notes: 设置错误返回信息
     * author: lfy
     * Date: 2021/10/11
     * Time: 10:58
     * @param bool $type
     * @return $this
     */
    public function setHttpErrors(bool $type): BaseService
    {
        $this->http_errors = $type;
        return $this;
    }

    /**
     * Notes: 获取授权地址
     * author: lfy
     * Date: 2021/10/11
     * Time: 10:40
     * @return Repository|Application|mixed
     */
    public function getAuthorizedUrl($state, $scope)
    {
        $scope = !empty($scope) ? $scope : config('platform.kwaixiaodian.appScope');
        return $this->base_url . "/oauth/authorize?app_id={$this->appKey}&scope={$scope}&response_type=code&redirect_uri={$this->redirect_uri}&state=${state}";
    }

    /**
     * Notes: 设置token
     * author: lfy
     * Date: 2021/10/11
     * Time: 10:36
     * @param $token
     * @return $this
     */
    public function setToken($token): BaseService
    {
        $this->token = $token;
        return $this;
    }

    /**
     * Notes:
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:31
     * @param int $maxRetryTimes
     */
    public function setMaxRetryTimes(int $maxRetryTimes): void
    {
        if ($maxRetryTimes < 0) {
            $maxRetryTimes = 0;
        } else if ($maxRetryTimes > 10) $maxRetryTimes = 10;
        $this->maxRetryTimes = $maxRetryTimes;
    }

    /**
     * Notes:
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:31
     * @param int $retryInterval
     */
    public function setRetryInterval(int $retryInterval): void
    {
        if ($retryInterval < 0) {
            $retryInterval = 0;
        }
        $this->retryInterval = $retryInterval;
    }

    /**Content-Type
     * Notes:   请求参数组装
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:28
     */
    private function getOptions($apiType): void
    {
        $this->commonParams['appkey']     = $this->appKey;
        $this->commonParams['version']    = $this->version;
        $this->commonParams['timestamp']  = $this->getMillisecond();
        $this->commonParams['method']     = trim($apiType);
        $this->commonParams['param']      = $this->apiParams ? json_encode($this->apiParams) : '{}';
        $this->commonParams['signMethod'] = $this->signMethod;
        if (!empty($this->token)) $this->commonParams['access_token'] = $this->token;
        ksort($this->commonParams); // 排序
        $this->commonParams['sign'] = $this->getSign($this->commonParams, $this->signSecret);
    }

    /**
     * Notes:生成请求地址
     * author: lfy
     * Date: 2021/11/19
     * Time: 14:07
     * @return string
     */
    private function createUrl(): string
    {
        $apiInfo = str_replace('.', '/', $this->commonParams['method']);
        return '/' . $apiInfo;
    }

    /**
     * Notes: 设置单个请求参数
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:31
     * @param string $key
     * @param $value
     */
    public function addApiParam(string $key, $value)
    {
        if (is_object($value) || is_array($value)) $value = json_encode($value, JSON_UNESCAPED_UNICODE);
        $this->apiParams[$key] = $value;
    }

    /**
     * Notes: 批量设置请求参数
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:31
     * @param array $apiParams
     * @return $this
     */
    public function setApiParams(array $apiParams): BaseService
    {
        foreach ($apiParams as $paramKey => $apiParam) $this->addApiParam($paramKey, $apiParam);
        unset($paramKey, $apiParam);
        return $this;
    }

    public function addCommonParams(string $key, $value)
    {
        if (is_object($value) || is_array($value)) $value = json_encode($value, JSON_UNESCAPED_UNICODE);
        $this->commonParams[$key] = $value;
    }

    /**
     * Notes: 设置当个头部请求信息
     * author: lfy
     * Date: 2021/10/8
     * Time: 16:19
     * @param string $key
     * @param string $value
     */
    public function addHeaderParam(string $key, string $value)
    {
        $this->headerParams[$key] = $value;
    }

    /**
     * Notes:   批量设置头部请求信息
     * author: lfy
     * Date: 2021/10/8
     * Time: 16:20
     * @param array $headerParams
     * @return $this
     */
    public function setHeaderParams(array $headerParams): BaseService
    {
        foreach ($headerParams as $key => $headerParam) {
            $this->addHeaderParam($key, $headerParam);
        }
        unset($key, $headerParam);

        return $this;
    }

    /**
     * Notes: 请求api
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:31
     * @return mixed|void
     * @throws GuzzleException
     */
    private function request(string $uri, $method = self::METHOD_POST, array $options = [])
    {
        try {

            $retryTimes = 0;
            while (true) try {
                if (!empty($this->headerParams)) $options['headers'] = $this->headerParams;

                if (!($method !== self::METHOD_GET) || !($method !== self::METHOD_PUT)) {
                    $options['query'] = $this->commonParams;
                    $this->apiParams  = [];
                }
                if (!empty($this->apiParams)) $options['form_params'] = $this->commonParams;
                $options['http_errors'] = $this->http_errors;
                $response = $this->_httpClient->request($method, $uri, $options);
                return $this->getResponse($response);
            } catch (Exception $e) {
                if ($retryTimes < $this->maxRetryTimes) {
                    $retryTimes += 1;
                    sleep($this->retryInterval);
                } else {
                    throw $e;
                }
            }
        } catch (Exception $ex) {
            Log::channel('kwaixiaodianServers')->error('Api 服务异常', ['msg' => $ex->getMessage(), 'code' => $ex->getCode(), 'args' => func_get_args(), 'trace' => $ex->getTraceAsString()]);
        }
    }

    /**
     * Notes: 请求结果解析
     * author: lfy
     * Date: 2021/10/7
     * Time: 10:30
     * @param $response
     * @return mixed
     * @throws Exception
     */
    protected function getResponse($response)
    {
        if ($response) {
            $result    = (string)$response->getBody()->getContents();
            $code      = $response->getStatusCode(); // 200
            $reason    = $response->getReasonPhrase(); // OK
            $xml_check = strpos($result, '<?xml') !== false;
            if ($xml_check) {
                $xml_namespace = strpos($result, '<ns3:') !== false ? 'ns3' : 'ns2';
                $result        = simplexml_load_string($result, null, 0, $xml_namespace, true);
                $result        = json_encode($result);
            }
            $result = json_decode($result, TRUE);
            return empty($result) ? ['statusCode' => $code, 'reasonPhrase' => $reason] : array_merge($result, ['statusCode' => $code]);
        } else {
            throw new Exception('没有回应');
        }
    }

    /**
     * Notes:
     * author: lfy
     * Date: 2021/11/19
     * Time: 14:31
     * @return array|mixed|void
     * @throws GuzzleException
     */
    public function getTokenData($uri='/oauth2/access_token')
    {
        $this->commonParams['app_id']     = $this->appKey;
        $this->commonParams['app_secret'] = $this->appSecret;
        return self::request($uri, self::METHOD_GET);
    }

    /**
     * Notes: GET请求方式
     * author: lfy
     * Date: 2021/9/25
     * Time: 20:57
     * @return bool|void
     * @throws GuzzleException
     */
    public function get($apiName, array $options = [])
    {
        $this->getOptions($apiName);
        return self::request($this->createUrl(), self::METHOD_GET, $options);
    }

    /**
     * Notes: POST请求
     * author: lfy
     * Date: 2021/9/25
     * Time: 20:57
     * @return bool|void
     * @throws GuzzleException
     */
    public function post($apiName, array $options = [])
    {
        $this->getOptions($apiName);
        return self::request($this->createUrl(), self::METHOD_POST, $options);
    }

    /**
     * Notes:
     * author: lfy
     * Date: 2021/10/11
     * Time: 11:23
     * @return mixed|void
     * @throws GuzzleException
     */
    public function put($apiName, array $options = [])
    {
        $this->getOptions($apiName);
        return self::request($this->createUrl(), self::METHOD_PUT, $options);
    }

    /**
     * Notes:
     * author: lfy
     * Date: 2021/10/11
     * Time: 11:23
     * @return mixed|void
     * @throws GuzzleException
     */
    public function delete($apiName, array $options = [])
    {
        $this->getOptions($apiName);
        return self::request($this->createUrl(), self::METHOD_DELETE, $options);
    }

    /**
     * Notes: 生成签名
     * author: lfy
     * Date: 2021/11/19
     * Time: 13:46
     * @param $params
     * @param $key
     * @return string
     */
    public function getSign($params, $key): string
    {
        $unSignParaString = $this->formatQueryParaMap($params, false);
        return (md5($unSignParaString . "&signSecret=" . $key));
    }

    public function formatQueryParaMap(array $paraMap, $urlEncode = false)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if (null != $v && "null" != $v) {
                if ($urlEncode) $v = urlencode($v);
                $buff .= $k . "=" . $v . "&";
            }
        }
        $reqPar = '';
        if (strlen($buff) > 0) $reqPar = substr($buff, 0, strlen($buff) - 1);
        return $reqPar;
    }

    /**
     * Notes:获取13位时间戳
     * author: lfy
     * Date: 2021/11/19
     * Time: 13:45
     * @return string
     */
    private static function getMillisecond(): string
    {
        list($t1, $t2) = explode(' ', microtime());
        return sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
    }

    /*
     *  请求基础设置结束
     */
}
